package per.vic.attachment.service;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import per.vic.attachment.annotation.AttachmentFlag;
import per.vic.attachment.autoconfigure.AttachmentProperties;
import per.vic.attachment.dao.AttachmentDao;
import per.vic.attachment.model.Attachment;
import pers.vic.boot.base.service.AttachmentServiceI;
import pers.vic.boot.util.CnToSpellUtils;
import pers.vic.boot.util.CommonUtils;
import pers.vic.boot.util.RegexUtil;
import pers.vic.boot.util.collections.CalcDiffCollection;

/**
 * @description: 附件处理service
 * @author: Vic.xu
 * @date: 2019年12月11日 下午1:37:30
 * TODO  implements AttachmentServiceI 怎么修改 和base项目的耦合
 */
@Service
public class AttachmentService implements AttachmentServiceI{

	static Logger logger = LoggerFactory.getLogger(AttachmentService.class);

	@Resource
	private AttachmentDao attachmentDao;

	@Autowired
	private AttachmentProperties attachmentProperties;

	/**
	 * 上传文件
	 * 
	 * @param file
	 * @param module
	 * @return
	 * @throws IOException
	 */
	public Attachment upfile(MultipartFile file, String module) throws IOException {
		if (file == null || file.isEmpty()) {
			return null;
		}
		Attachment attachment = createAttachentFile(file.getOriginalFilename(), module);
		attachment.setSize((int) (file.getSize() / 1024));// Kb
		// copy文件流到服务器
		File realFile = attachment.getRealFile();
		FileUtils.copyInputStreamToFile(file.getInputStream(), realFile);
		attachmentDao.insert(attachment);
		attachment.setUrl(attachmentProperties.getVisit() + "/attachment/visit/" + attachment.getId());
		return attachment;

	}

	/**
	 * 根据文件原始名称创建附件的文件: 存储位置 = 配置的存储地址 + 模块 + 年/月/日 + 文件
	 * 
	 * @param filename
	 * @param module   文件属于哪个模块
	 * @throws IOException
	 */
	private Attachment createAttachentFile(String originalFilename, String module) throws IOException {
		originalFilename = originalFilename.replaceAll("[///?:@=&]", "");// 去掉一些特殊的字符
		String filename = CnToSpellUtils.getFullSpell(originalFilename);
		filename = UUID.randomUUID().toString().replace("-", "") + "-" + filename;// 生成新的文件名
		String position = attachmentProperties.getPosition();// 存放的位置
		Calendar c = Calendar.getInstance();
		int year = c.get(Calendar.YEAR);
		int month = c.get(Calendar.MONTH) + 1;
		int day = c.get(Calendar.DATE);
		StringBuilder relativePath = new StringBuilder();
		if (StringUtils.isNotBlank(module)) {
			relativePath.append(module).append(File.separator);
		}
		relativePath.append(year).append(File.separator).append(month).append(File.separator).append(day);
		// 文件相对路径/绝对路径/文件名
		Attachment attachment = new Attachment(position, relativePath.toString(), filename);
		// ContentType
		attachment.setContentType(CommonUtils.guessContentTypeFromName(originalFilename));
		// originalFilename
		attachment.setOriginalName(originalFilename);
		attachment.setModule(module);
		attachment.setTemporary(1);
		return attachment;
	}

	/**
	 * 获取配置的附件主机
	 */
	public String getHost() {
		String host = attachmentProperties.getHost();
		if (StringUtils.isBlank(host)) {
			HttpServletRequest request = CommonUtils.currentRequest();
			return request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + "/"
					+ request.getContextPath();
		}
		return host;
	}

	/** 修改附件状态 */
	public void updateTemporary(boolean temporary, int id) {
		attachmentDao.updateTemporary(id, temporary);
	}

	/** 修改附件状态 */
	public void updateTemporary(boolean temporary, List<Integer> ids) {
		if (CollectionUtils.isEmpty(ids)) {
			return;
		}
		String idsStr = ids.stream().map(String::valueOf).collect(Collectors.joining(","));
		attachmentDao.updateTemporary(idsStr, temporary);
	}

	public Attachment selectAttachmentById(Integer id) {
		return attachmentDao.selectAttachmentById(id);
	}

	/**
	 * 当访问不存在的附件的时候 返回一个特定的附件:空附件
	 * 
	 * @return
	 */
	public String getBrokenAttachment() {
		String broken = attachmentProperties.getBroken();
		if (StringUtils.isEmpty(broken)) {
			throw new RuntimeException("不没有配置空附件的相对地址");
		}
		return getHost() + broken;
	}

	/***********************************************************************************************/
	/**
	 * 从对象中获取附件的id集合
	 * 
	 * @see AttachmentFlag
	 */
	public static <T> List<Integer> getAttachmentIds(T t) {
		List<Integer> list = new ArrayList<Integer>();
		if (t == null) {
			return list;
		}
		try {
			Class<?> clazz = t.getClass();
			for (; clazz != Object.class; clazz = clazz.getSuperclass()) {// 向上遍历父类

				Field[] fileds = clazz.getDeclaredFields();// 暂时不考虑父类的
				for (Field f : fileds) {
					if (f.isAnnotationPresent(AttachmentFlag.class)) {
						AttachmentFlag flag = f.getAnnotation(AttachmentFlag.class);
						PropertyDescriptor pd = new PropertyDescriptor(f.getName(), t.getClass());
						Method method = pd.getReadMethod();// 获得get方法
						Object value = method.invoke(t);
						if (value == null || value.toString().equals("")) {
							continue;
						}
						switch (flag.value()) {
						case SIGN:
							if ((value.toString()).matches("\\d+")) {
								list.add(Integer.parseInt(value.toString()));
							}
							break;
						case SIGNS:
							list.addAll(CommonUtils.toIntList(value.toString().split(",")));
							break;
						case CONTENT:
							Integer[] arr = getAttachmentIds(value.toString());
							if (arr != null && arr.length > 0) {
								list.addAll(new ArrayList<Integer>(Arrays.asList(arr)));
							}
							break;
						default:
							logger.info("反射{}字段{}获得的值为{}", new Object[] { t.getClass(), f.getName(), value });
							break;
						}
					}
				}
			}
		} catch (Exception e) {
			logger.error(ExceptionUtils.getStackTrace(e));
		}
		return list;
	}

	/** 获得文本内容中的附件id数组 */
	public static Integer[] getAttachmentIds(String content) {
		List<String> list = RegexUtil.getList(content, AttachmentProperties.textAttachmentReg, 1);
		if (list.isEmpty()) {
			return new Integer[] {};
		}
		List<Integer> intList = CommonUtils.toIntList(list.toArray(new String[0]));
		int size = intList.size();
		Integer[] arr = new Integer[size];
		for (int i = 0; i < size; i++) {
			arr[i] = intList.get(i);
		}
		return arr;
	}

	public static void main(String[] args) {
		String content = "/attachment/visit/1\"";
		content += "/attachment/download/2\"";
		String DEFAULT_ATTACHMENT_REG = "/attachment/(download|visit)/(\\d+)\"";
		System.out.println(content.matches(DEFAULT_ATTACHMENT_REG));
		List<String> list = RegexUtil.getList(content, DEFAULT_ATTACHMENT_REG, 2);
		String ids = list.stream().map(String::valueOf).collect(Collectors.joining(","));
		System.out.println(ids);
	}

	/** 新增对象中的全部附件 */
	public <T> boolean addAttachmengFromObj(T t) {
		List<Integer> idList = getAttachmentIds(t);
		if (idList.isEmpty()) {
			return false;
		}
		String ids = idList.stream().map(String::valueOf).collect(Collectors.joining(","));
		attachmentDao.updateTemporary(ids, false);
		return true;
	}

	/***
	 * 删除对象中的全部附件
	 */
	public <T> void deleteAttachmengFromObj(List<T> ts) {
		if (ts == null) {
			return;
		}
		List<Integer> list = new ArrayList<Integer>();
		for (T t : ts) {
			list.addAll(getAttachmentIds(t));
		}
		updateTemporary(true, list);
	}

	// 删除对象中的全部附件
	public <T> void deleteAttachmengFromObj(T t) {
		updateTemporary(true, getAttachmentIds(t));
	}

	/**
	 * 分开对象中要删除和要新增的附件 需要 AttachmentFlag 注解
	 * 
	 * @param old
	 * @param now
	 * @return
	 */
	public <T> void handleOldAndNowAttachment(T old, T now) {
		CalcDiffCollection<Integer> data = CalcDiffCollection.instance(getAttachmentIds(old), getAttachmentIds(now));
		updateTemporary(true, data.getOnlyInOld());
		updateTemporary(false, data.getOnlyInNew());

	}

}
